#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <iostream>
#include <llvm/Support/system_error.h>

#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Module.h>
#include <llvm/LLVMContext.h>
#include <llvm/Linker.h>
#include <llvm/PassManager.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/Scalar.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/JIT.h>
#include <llvm/ExecutionEngine/Interpreter.h>
//#include <llvm/ExecutionEngine/MCJIT.h>

#include <llvm/Support/DynamicLibrary.h>

#include "JIT.h"
#include "../HMC.h"
#include "../../6502.h"
#include "../../NESMemory.h"

extern __Context CPU;
extern const InstructionData Instructions[256];
extern uint8_t (*AddressingModeDecoders[13])();

using namespace llvm;

//static const char* CompilationCmd = "echo \"%s\" | clang -xc -c - -o - -emit-llvm | opt - -strip-debug -f\0";
static const char* CompilationCmd = "echo \"%s\" | clang -fPIC -xc -c - -o - -emit-llvm\0";

LLVMContext *LLVMCtxt;
OwningPtr<MemoryBuffer> *IR6502BitCode;

void JIT_Init(void){
    std::string ErrStr;
    
    ErrStr.reserve(255);
    
    LLVMCtxt = new LLVMContext; 
    IR6502BitCode = new OwningPtr<MemoryBuffer>;
    MemoryBuffer::getFile("6502.bc", *IR6502BitCode);
    llvm::InitializeNativeTarget();
    
    llvm::sys::DynamicLibrary::AddSymbol("CPU", (void*)&CPU);
    llvm::sys::DynamicLibrary::AddSymbol("Instructions", (void*)&Instructions);
    llvm::sys::DynamicLibrary::AddSymbol("AddressingModeDecoders", (void*)AddressingModeDecoders);
    llvm::sys::DynamicLibrary::LoadLibraryPermanently("./NESMemory.so", &ErrStr);
    
    //std::cout << ErrStr << "\n";
}

void* JIT_CompileToIRModule(char* BlockCode){
    void* Buffer;
    FILE* ClangPipe;
    char* ClangCmd;
    uint32_t BitCodeSize;
    StringRef* BitCode;
    MemoryBuffer* BCMemoryBuffer;
    Module* Module;
            
    ClangCmd = (char*) calloc (0x1000, 1);
    Buffer = (void*) calloc(0x10000, 1);
    
    //Create pipe to Clang and read BitCode from it
    sprintf(ClangCmd, CompilationCmd, BlockCode);
    ClangPipe = popen(ClangCmd, "r");
    BitCodeSize = fread(Buffer, 1, 0x10000, ClangPipe);
    
    //Setup bitcode in LLVM
    BitCode = new StringRef((const char*)Buffer, BitCodeSize);
    BCMemoryBuffer = MemoryBuffer::getMemBuffer(*BitCode,"", true);
   
    //Parse the LLVM bitcode and get the module
    Module = ParseBitcodeFile(BCMemoryBuffer, *LLVMCtxt, NULL);
    
    delete BCMemoryBuffer;
    delete BitCode;
    
    pclose(ClangPipe);
    
    free(ClangCmd);
    free(Buffer);
    
    return (void*) Module;
}

void* JIT_LinkIRModuleToRuntime(void *IRModule){
    Linker *L;
    Module *IR6502;
    PassManager* PM;
    
    //Get the IR6502 Instruction Data
    IR6502 = ParseBitcodeFile((*IR6502BitCode).get(), *LLVMCtxt);
    
    //Link the current module with IR6502 Instruction Data, preserving the current Module
    Linker::LinkModules(IR6502, (Module*)IRModule, llvm::Linker::PreserveSource, NULL);
    
    PM = new PassManager();
    
    //Inline all instructions into the IR Module.
    PM->add(createInternalizePass(true));
    PM->add(createAlwaysInlinerPass());
    
    PM->run(*IR6502);
    
    delete PM;
    
    IR6502->dump();
    
    return (void*) IR6502;
}

void* JIT_LinkIRModuleToContext(void* IRModule, char* ContextCode){
    Module* ContextModule;
    Linker* L;
    PassManager* PM;
    
    ContextModule = (Module*) JIT_CompileToIRModule(ContextCode);
    
    Linker::LinkModules( (Module*) IRModule, ContextModule, llvm::Linker::DestroySource, NULL);
    
    PM = new PassManager();
    
    PM->add(createInternalizePass(true));
    PM->add(createAlwaysInlinerPass());
    
    PM->run(*((Module*)IRModule));
    
    delete PM;
    
    return IRModule;
}

HMC* JIT_GetHMC(void* IRModule, uint16_t Addr){
    HMC* MachineCode;
    std::string ErrStr;
    Function* F;
    
    char FunctionName[0x100];
    
    MachineCode = HMCNew();
    
    ErrStr.reserve(255);
    
    MachineCode->LLVMEE = llvm::EngineBuilder((Module*) IRModule)
            .setErrorStr(&ErrStr)
            .setEngineKind(llvm::EngineKind::JIT)
            .setOptLevel(llvm::CodeGenOpt::None)
            //.setUseMCJIT(true)
            //.setMCPU("corei7-avx")
            .create();
       
    if(!MachineCode->LLVMEE){
        fprintf(stderr, "Failed to construct LLVM Execution Engine: %s\n", ErrStr.c_str());
        exit(1);
    }
    
    //StructType *myType = ((Module*)IRModule)->getTypeByName("__Context");
    
    //MachineCode->LLVMEE->addGlobalMapping(myType, &CPU);
    
    sprintf(FunctionName, "__NES__%04X__", Addr);
    
    F = ((Module*)IRModule)->getFunction(FunctionName);
    
    MachineCode->BlockCode = (uint32_t(*)(int32_t)) MachineCode->LLVMEE->getPointerToFunction(F);
    
    return MachineCode;
}
